声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另外因为是理解翻译,肯定会有错误的地方,欢迎指正。
欢迎转载,转载请注明出处,谢谢!
控制反转容器
基础绑定
上一张,我们学习了依赖注入,接下来,我们继续探索的是“控制反转”或者叫“依赖倒置”。后面我们使用IoC容器来代指如上的定义。IoC容器使类的依赖管理变的非常方便,Laravel的核心就是由这种强大的容器思想来驱动的。IoC容器是Laravel框架重要的组成部分,他将框架中所有组件组织在一起工作。事实上Laravel的Application
类就是集成自Container
容器类。
控制反转容器(IoC Container)
控制反转使依赖注入变得更加便捷。如果在容器中定义好了相关的类或者接口(约定),我们如何在程序中解析、并注入这些对象呢?
Laravel应用中,IoC容器可以使用依照门面设计模式实现的App
类来访问容器。容器中包含了各式各样的方法,这里我们只介绍一些比较基础的方法。让我们以上一章中的BillerInterface
和BillingNotifierInterface
为基础,来继续探讨使用Stripe1实现的支付功能。我们可以将Stripe的接口实现按照如下代码绑定到容器中:
App::bind('BillerInterface', function()
{
return new StripeBiller(App::make('BillingNotifierInterface'));
})
注意这里,我们在绑定了BillerInterface
的同时,也注入了BillingNotifierInterface
接口实现的具体类,所以要将接口绑定到容器中:
App::bind('BillingNotifierInterface', function()
{
return new EmailBillingNotifier;
});
如上,我们可以理解,容器就是各种接口对应实现类的绑定的地方。一旦他们绑定到容器中,我们就能在整个应用中的任意地方解析并使用他。我们甚至可以在解析器中继续将其他内容绑定到容器。
有瑕疵?
Laravel控制反转容器是Fabien Potencier实现的Pimple2控制翻转容器的一种替代方案。如果你已经在项目中使用到Pimple,尽可安心的升级为Illuminate Container3组件,他为您提供了更多好用的特性!
一旦使用了容器,切换接口实现就是一件非常简单的事情,简单到一行代码就能搞定:
class UserController extends BaseController{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
}
控制器通过容器实例化后,包含EmailBillingNotifier
的StripeBiller
类就会随着容器注入到控制器中。现在,若想更换通知器的实现方式,只需要接口绑定的实现即可:
App::bind('BillingNotifierInterface', function()
{
return new SmsBillingNotifier;
});
现在,无需担心项目中到底哪里用到了这个通知器,只需实现新的SmsBillingNotifier
类即可。利用这种方式,我们的应用可以在不同的场景下实现快速切换。
是不是感觉这种切换实现的方式很高大上。想象一下,如果想将短信通知器的服务提供商更换为Twilio。我们只须开发一个使用Twilio通知的实现类,并替换掉绑定到容器中的接口对应的实现类就好。如果在向Twilio的过度中出现了问题,我们还能快速的将容器中接口绑定的类替换回原来的服务,这里只需要那么一丁点改变就能快速实现需求变更。可以看到,依赖注入的优点是超乎想象的。再多几个例子?
好吧!接着往下看。 有时候,我们想在整个应用中对一个类只进行一次解析,一次实例化。使用容器中的singleton
方法即可:
App::singleton('BillingNotifierInterface', function()
{
return new SmsBillingNotifier;
});
现在,容器一蛋解析了订单通知类,在接下来的整个请求中都会使用同一个已实例化的实例。
容器中的intance
方法和singleton
有点类似;区别在于你可以传入一个已存在的对象来更新接口绑定的实例,在后续的使用中,容器都将使用到这个新的对象。
$notifier = new SmsBillingNotifier;
App::instance('BillingNotifierInterface', $notifier);
现在我们已经熟悉使用容器进行闭包回调的基础方法,接下来,让我们深入挖掘下他更强大的功能:反射。
容器的独立使用
即使没有使用Laravel框架,我们仍然可以在项目中使用
Composer
安装illuminate/container
组件来使用Laravel的控制反转容器。
反射
Laravel容器的一个强大的特性就是能通过反射自动解析依赖。反射具有检测类及其方法的能力。比如,PHP中的ReflectionClass
类允许你检测一些方法在给定的类中是否可用。PHP函数method_exists
也是反射的一种形式。看看下面的代码,让我们来把玩一下:
$reflection = new ReflectionClass('StripeBiller');
var_dump($reflection->getMethods());
var_dump($reflection->getConstants());
通过使用PHP的这种特性,Laravel可以实现一些有趣的功能!例如,如下代码:
class UserController extends BaseController {
public function __construct(StripeBiller $biller)
{
$this->biller = $biller;
}
}
如上控制器初始化时需要传入StripeBiller类型的对象,我们可以通过反射进行类型检测。当Laravel容器没有绑定相应的解析器,它就会通过反射尝试解析该类。流程大致如下:
容器中有无StripeBiller解析器?
没有解析器?映射类
StripeBiller
判断其依赖。递归的解析
StripeBiller
类的所有依赖。通过
ReflectionClass->newInstanceArgs()
实例化一个新的StripeBiller
。
可以看到,容器为你做了很多繁重的工作,使你能释放更多的时间用于编码各种逻辑的代码类。这就是Laravel容器特有的强大特性,也是它能够胜任构建大型应用的必杀器。
现在,我们控制器中的代码修改成这样,这会怎样?
class UserController extends BaseController
{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
}
假如我们没有对BillerInterface
进行绑定,那么容器如何注入其依赖的类呢?注意,接口只是个约定,他是不能进行实例化的。在没有给定任何信息的情况下,容器是无法实例化相关依赖的。所以我们需要使用bind
方法来为接口指定一个默认的类的实现:
App::bind('BillerInterface','StripBiller');
这里我们把字符串替换成一个闭包传入容器,他会告诉容器任何情况下总是使用StripeBiller
这个实现自BillerInterface
接口的类。这里,我们又可以只修改一行代码,就能进行替换掉容器中的绑定的逻辑了。假如我们想使用余额支付来代替现有的支付,我们只需要完成继承自BillerInterface
接口的实现类BalanceInterfacez
,同时修改下容器中的绑定:
App::bind('BillerInterface', 'BalancedBiller');
应用就会自动解析并使用这个新的支付方式。
同样我们也可以使用singleton
方法绑定接口,这样在整个请求周期内容器只会进行一次实例化。
App::singleton('BillerInterface', 'StripeBiller');
掌握容器
想要深入理解Laravel容器?那就通读下代码吧!容器只有一个类文件
IlluminateContainerContainer
。当你看完这个文件代码后,肯定能对容器有更深、更全面的认识。
-
2015-04-02 第二次阅读修正。
2015-02-14 第一次翻译发布。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。